package com.fstec.ks.master.netty;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;

import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

import com.fstec.ks.master.MasterEngine;
import com.sinotn.SinotnLogger;
import com.sinotn.SinotnRuntime;
import com.sinotn.error.MessageException;
import com.sinotn.ks.definition.Computer;
import com.sinotn.ks.definition.RequestMessage;
import com.sinotn.ks.netty.ConnectException;
import com.sinotn.ks.netty.NettyLogger;
import com.sinotn.ks.netty.handler.ExamHandler;
import com.sinotn.ks.netty.message.SimpleMessage;

/**
 * 消息处理器
 * @Title MessageHandler.java
 * @Package com.fstec.ks.master.netty
 * @Description 
 * Copyright: Copyright (c) 2023
 * Company:广东峰上科技有限公司
 * 
 * @author <a href="mailto:xieqj@fstec.com">谢启进</a>
 * @date 2023年10月10日 上午10:50:15
 * @version V1.0
 */
public class MessageHandler extends ExamHandler {
	public static final AttributeKey<Computer> SESSION_KEY=AttributeKey.valueOf("_sinotn.netty.session");
	private MasterEngine masterEngine;
	private boolean debug=false;

	public MessageHandler(MasterEngine masterEngine){
		this.masterEngine=masterEngine;
		File file=new File(SinotnRuntime.getBinFolder(), "debug.dt");
		this.debug=file.exists();
	}

	/**
	 * 客户端电脑接入考试系统
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		Channel channel=ctx.channel();
		DefaultComputer computer=new DefaultComputer(channel);
		this.masterEngine.onComputerConnect(computer);
		ctx.channel().attr(SESSION_KEY).set(computer);
		ctx.fireChannelActive();
	}

	/**
	 * 客户端电脑断开考试系统
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		ctx.fireChannelInactive();
		Channel channel=ctx.channel();
		Computer computer=channel.attr(SESSION_KEY).getAndRemove();
		if(null!=computer){
			try{
				this.masterEngine.onComputerDisconnect(computer);
			}catch(Throwable e){
				SinotnLogger.DEBUG.error("响应考试机断开连接事件发生系统错误",e);
			}
			computer.release();
		}
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		if(cause instanceof DecoderException){
			this.release();
		}
		Channel channel=ctx.channel();
		if(channel==null){
			this.exceptionProcess("unknow-channelId", ctx, cause);
		}else{
			Computer computer=channel.attr(SESSION_KEY).get();
			if(computer!=null){
				this.masterEngine.onSocketException(computer,cause);
			}else{
				this.exceptionProcess(channel.id().asLongText(), ctx, cause);
			}
		}
	}

	private void exceptionProcess(String channelId,ChannelHandlerContext ctx, Throwable cause) throws Exception{
		if(cause instanceof DecoderException){
			this.release();
			SinotnLogger.DEBUG.error("数据解码发生系统错误"+channelId,cause);
			//出现解码异常则关闭当前channel
			ctx.close();
		}else if(cause instanceof IOException){
			String msg=cause.getMessage();
			if(msg.indexOf("强迫关闭")==-1&&msg.indexOf("中止")==-1){
				SinotnLogger.DEBUG.error("客户端程序发生IOException:"+channelId, cause);
			}/*else{
				SinotnLogger.DEBUG.error("客户端程序强迫关闭:"+channelId);
			}*/
		}else{
			SinotnLogger.DEBUG.error("客户端程序发生通讯异常:"+channelId, cause);
		}
	}

	private String getIpAddr(Channel channel){
		String ipAddr="0.0.0.0";
		SocketAddress socketAddr=channel.remoteAddress();
		if((null!=socketAddr)&&(socketAddr instanceof InetSocketAddress)){
			InetSocketAddress inetSocket=(InetSocketAddress)socketAddr;
			InetAddress inetAddr=inetSocket.getAddress();
			if(null!=inetAddr){
				ipAddr=inetAddr.getHostAddress();
			}
		}
		return ipAddr;
	}

	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
		if (evt instanceof IdleStateEvent) {//超时事件
			IdleStateEvent idleEvent = (IdleStateEvent) evt;
			if (idleEvent.state() == IdleState.ALL_IDLE) {//读写空闲超时
				// 在指定时间未有客户端数据读写通讯且是未注册客户端，断开与客户端连接
				try{
					Channel channel=ctx.channel();
					Computer computer=channel.attr(SESSION_KEY).get();
					if(computer==null||computer.getRegKey()==null){
						channel.close();
					}
				}catch(Throwable e){
					SinotnLogger.DEBUG.error("定时清理空闲未注册连接异常", e);
				}
			}
		}
		super.userEventTriggered(ctx, evt);
	}

	/**
	 * 接收到客户端电脑消息体
	 */
	@SuppressWarnings({"unchecked"})
	@Override
	public void messageRead(ChannelHandlerContext ctx, RequestMessage msg) throws Exception {
		final Channel channel=ctx.channel();
		Computer computer=channel.attr(SESSION_KEY).get();
		if(computer==null){
			SinotnLogger.DEBUG.error("未获取会话客户端计算实例，断开与客户端连接@"+getIpAddr(channel));
			channel.close();
			return;
		}
		try{
			this.masterEngine.onMessageReceive(computer,msg);
		}catch(ExamExceptionMessage errMsg){
			if(errMsg.getMethod()==null){
				errMsg.setMethod("alert");
			}
			if(errMsg.getToken()==0){
				errMsg.setToken(msg.getToken());
			}
			channel.writeAndFlush(errMsg);
			if(errMsg.isDisconnect()){
				channel.close();
				/*final ExamExceptionMessage finalErr=errMsg;
				channelFuture.addListener(new GenericFutureListener() {
					@Override
					public void operationComplete(Future future) throws Exception {
						if(future.isDone()&&future.isSuccess()){
							channel.close();
						}else{
							Throwable e=future.cause();
							if(e!=null){
								SinotnLogger.DEBUG.error("发送错误消息发生系统错误"+finalErr,e);
							}
						}
					}
				});*/
			}
		}catch(MessageException e){
			SimpleMessage sm=new SimpleMessage();
			sm.setText(e.getMessage());
			sm.setToken(msg.getToken());
			sm.setType(e.getCode());
			computer.sendMessage(sm);
		}catch(Throwable e){
			SinotnLogger.DEBUG.error("考试引擎处理消息发生系统错误"+msg.toString()+"@"+computer.toString(),e);
			ExamExceptionMessage ees=new ExamExceptionMessage(ResponseMessageType.ERR_SYSTEM, "考试引擎处理消息发生系统错误");
			ees.setMethod("alert");
			ees.setToken(msg.getToken());
			ChannelFuture channelFuture=channel.writeAndFlush(ees);
			channelFuture.addListener(new DefaultListener());
		}
	}

	@Override
	protected void validMessageType(ChannelHandlerContext ctx,int type,long token) {
		if(this.debug){
			return;
		}
		if(200<type){
			Channel channel=ctx.channel();
			Computer computer=channel.attr(SESSION_KEY).get();
			if(computer==null){
				ConnectException e=new ConnectException("消息类型["+type+"]解码受限，原因:未注册会话客户端");
				e.setToken(token);
				e.setType(type);
				throw e;
			}else if(computer.getRegKey()==null){
				ConnectException e=new ConnectException("消息类型["+type+"]解码受限，原因:未注册客户端密钥");
				e.setToken(token);
				e.setType(type);
				throw e;
			}
		}
	}
	@Override
	protected void validFileAuth(ChannelHandlerContext ctx, RequestMessage message) throws ConnectException {
		if(this.debug){
			return;
		}
		Channel channel=ctx.channel();
		Computer computer=channel.attr(SESSION_KEY).get();
		if(computer==null){
			NettyLogger.DEBUG.error("文件解码受限，原因:未注册会话客户端"+message.getText()+"@"+this.getIpAddr(channel));
			ConnectException e=new ConnectException("文件解码受限，原因:未注册会话客户端");
			e.setToken(message.getToken());
			e.setType(message.getType());
			throw e;
		}else if(computer.getRegKey()==null){
			NettyLogger.DEBUG.error("文件解码受限，原因:未注册客户端密钥"+message.getText()+"@"+this.getIpAddr(channel));
			ConnectException e=new ConnectException("文件解码受限，原因:未注册客户端密钥");
			e.setToken(message.getToken());
			e.setType(message.getType());
			throw e;
		}
	}
}
